#include <stdio.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

// #include <pthread.h>

#define SERVER_PORT 9999

struct clientNode
{
    char clientAcount[256];        // 客户端用户名
    char password[256];            // 客户端密码
    int newClientFd;               // 连接上来的客户端的套接字文件描述符
    struct sockaddr_in clientAddr; // 客户端的IP地址和端口号
};

void sys_err(const char *err)
{
    fprintf(stderr, "%s\n", strerror(errno));
    exit(0);
}

int main()
{
    int ret, maxFd, i = 0, imax = 0;
    int client[FD_SETSIZE]; // 存储所有客户端的文件描述符//该宏 系统已经定义好了 文件描述符的总数 1024

    struct sockaddr_in clientAddr; // 存储连接上来的客户端的IP地址和端口号
    int len = sizeof(struct sockaddr_in);

    printf("服务器 Port:%d PID:%d \n", SERVER_PORT, getpid());

    // 1、建立套接字
    int socketFd = socket(AF_INET, SOCK_STREAM, 0);
    if (socketFd == -1)
    {
        sys_err("socket error");
    }
    // 端口复用
    int optval = 1;
    setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    // 2、绑定自己的IP地址和端口号
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT); // short
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    ret = bind(socketFd, (struct sockaddr *)&serverAddr, sizeof(struct sockaddr_in));
    if (ret == -1)
    {
        sys_err("bind error");
    }

    // 3、设置监听
    listen(socketFd, 128);

    // 定义文件描述符集合
    fd_set rset, allset;
    // 清空集合
    FD_ZERO(&allset);

    FD_SET(socketFd, &allset); // 把监听套接字 加入到集合中
    // 最大的文件描述符
    maxFd = socketFd;

    // 初始化为 -1
    for (int k = 0; k < FD_SETSIZE; k++)
    {
        client[k] = -1;
    }

    while (1)
    {
        rset = allset; // 每次循环的时候都需要重新设置select的集合
        // 多路复用 ，同时监听 多个套接字文件描述符的状态  --阻塞监听
        int nready = select(maxFd + 1, &rset, NULL, NULL, NULL);
        if (nready == -1)
        {
            if (errno == EINTR) // UDP协议采用信号驱动模式，会使select被信号中断
                continue;
            else
            {
                perror("select error");
                break;
            }
        }
        // 程序走到这里，说明 集合中的文件描述符的状态  一定 发生变化了

        // 如果是监听套接字文件描述符发生变化了，说明一定是有新客户端连接上来了
        if (FD_ISSET(socketFd, &rset))
        {
            // 接收新的客户端
            int newClientFd = accept(socketFd, (struct sockaddr *)&clientAddr, &len);

            printf("有新的客户端连接上来  IP:%s Port:%hu newClientFd:%d\n",
                   inet_ntoa(clientAddr.sin_addr),
                   ntohs(clientAddr.sin_port),
                   newClientFd);

            // 把新的客户端文件描述符加入到 集合中
            FD_SET(newClientFd, &allset);
            // 更新文件描述符最大值
            if (newClientFd > maxFd)
                maxFd = newClientFd;

            // 将连接上来的客户端文件描述符 加入到 数组中
            for (i = 0; i < FD_SETSIZE; i++)
            {
                if (client[i] < 0)
                {
                    client[i] = newClientFd;
                    break;
                }
            }

            // imax = i;

            // 说明 该集合中 只有监听套接字 文件描述符发生变化
            if (--nready == 0)
                continue;
        }

        // 程序走到这里，说明 有客户端 发送数据过来
        for (i = 0; i < FD_SETSIZE; i++)
        {
            if (client[i] < 0)
                continue;

            // 说明该客户端发送数据过来了
            if (FD_ISSET(client[i], &rset))
            {
                char buf[1024] = {0};
                int ret = read(client[i], buf, sizeof(buf));
                if (ret == 0) // 该客户端断开了
                {
                    printf("有客户端断开了.....\n");
                    close(client[i]); // 关闭文件描述符

                    FD_CLR(client[i], &allset); // 将该客户端从 文件集合中删除
                    client[i] = -1;             // 该文件描述符对应的数组 位置 置 -1
                }

                printf("recv:%s\n", buf);

                // 说明所有发生变化的文件描述符 已经被我们处理完了，则退出
                if (--nready == 0)
                    break;
            }
        }
    }

    // 关闭
    close(socketFd);

    return 0;
}